package cz.apnetis.gwt.client;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;

/** Table which can be editable and can be user as result
 * @author Lukas "benzin" Benda <a href="mailto:bendal@apnet.cz">bendal@apnet.cz</a>
 * @since Oct 2, 2010
 */
public class ResultTable<T, F extends TableModel> extends Composite implements HasWidgets, Repaintable {

  /** Name of style for result table */
  public static final String DEFAULT_STYLE_NAME = "result_table";
  /** Name of style for result table */
  public static final String DEFAULT_STYLE_NAME_GRID = "grid";
  /** Name of style for odd row */
  public static final String DEFAULT_STYLE_NAME_ROW_ODD = "row_odd";
  /** Name of style for even row */
  public static final String DEFAULT_STYLE_NAME_ROW_EVEN = "row_even";
  /** Name of style for row which contains sumarization */
  public static final String DEFAULT_STYLE_NAME_ROW_SUM = "row_sum";

  /** Model with data for table */
  private final F tableModel; public F getTableModel() { return this.tableModel; }
  /** Holder of header in table */
  private TableColumnHeaders columnHeaders; public void setColumnHeaders(final TableColumnHeaders columnHeaders) { this.columnHeaders = columnHeaders; } public TableColumnHeaders getColumnHeaders() { return this.columnHeaders; }
  /** Table Header */
  private TableHeader tableHeader; public void setTableHeader(final TableHeader tableHeader) { this.tableHeader = tableHeader; } public TableHeader getTableHeader() { return this.tableHeader; }

  /** All widgets in table */
  private final List<Widget> widgets = new ArrayList<Widget>();

  /** GUI representation of table */
  private Grid table = new Grid(); public final Grid getTable() { return this.table; }
  private final FlowPanel holderPanel = new FlowPanel();

  /** Count of visible column in table. This method is owervritable by child */
  protected int columnCount() {
    return getColumnHeaders().size();
  }

  /** Default constructor */
  public ResultTable(final F tableModel) {
    this.holderPanel.setStyleName(DEFAULT_STYLE_NAME);

    this.table.setStyleName(DEFAULT_STYLE_NAME_GRID);
    this.tableModel = tableModel;
    this.tableHeader = new TableHeader(this);
    this.tableHeader.init();
    addWidgetToPanel();
    initWidget(this.holderPanel);
  }

  /** This method fill table with widgets - paint table. Table model must be defined. */
  public final void paint() {
    if (getTableModel() == null) { throw new IllegalStateException("Table model must be defined when paint method is called."); }

    this.table.clear();

    getColumnHeaders().setResultTable(this);
    getColumnHeaders().initializeSettingImage();

    final int rowShift = getColumnHeaders() != null ? 1 : 0;

    this.table.resize(getTableModel().getRowCount() + rowShift, columnCount());

    this.tableHeader.init();
    getTableHeader().setPositionText(getTableModel().getResultPosition());

    getColumnHeaders().appendHeader(this.table);

    for (int row = 0; row < getTableModel().getRowCount(); row++) { repaintRow(row); }
  }

  @Override
  public final void repaint() { paint(); }

  /** Repaint single row of table */
  public void repaintRow(final int row) {
    final int rowShift = getColumnHeaders() != null ? 1 : 0;

    setRowStyle(row + rowShift, row);

    for (int col = 0; col < columnCount(); col++) {
      this.table.setWidget(row + rowShift, col, fieldWidget(row, col));
    }
  }

  protected void setRowStyle(final int tableRow, final int modelRow) {
    final boolean isSum = (getTableModel() instanceof TableModelSum) && ((TableModelSum) getTableModel()).isSumRow(modelRow);

    if (isSum) { this.table.getRowFormatter().setStyleName(tableRow, DEFAULT_STYLE_NAME_ROW_SUM);
    } else if (modelRow % 2 == 0) { this.table.getRowFormatter().setStyleName(tableRow, DEFAULT_STYLE_NAME_ROW_ODD); }
    else { this.table.getRowFormatter().setStyleName(tableRow, DEFAULT_STYLE_NAME_ROW_EVEN); }
  }

  /** Return widget for given field. This method is designed for override
   * @param row row which define field
   * @param col index of column which define field
   * @return widget which is in given table field
   */
  protected Widget fieldWidget(final int row, final int col) {
    final String column = getColumnHeaders().getVisibleColumns().get(col);
    return getTableModel().getFieldWidget(row, column);
  }

  private final void addWidgetToPanel() {
    this.holderPanel.clear();
    this.holderPanel.add(getTableHeader());
    this.holderPanel.add(this.table);
  }

  @Override
  public void add(final Widget w) {
    if (w instanceof TableColumnHeaders) {
      setColumnHeaders((TableColumnHeaders) w);
    } else if (w instanceof Grid) {
      this.table = (Grid) w;
      addWidgetToPanel();
    } else if (w instanceof TableHeader) {
      this.tableHeader = (TableHeader) w;
      addWidgetToPanel();
    }

    this.widgets.add(w);
  }

  @Override
  public void clear() {
    this.widgets.clear();
  }

  @Override
  public Iterator<Widget> iterator() {
    return this.widgets.iterator();
  }

  @Override
  public boolean remove(final Widget w) {
    return this.widgets.remove(w);
  }

  /** Default implementation of result table model, which show every data
   * which are insert to it and for each field make EditableLabel
   * @author Lukas "benzin" Benda <a href="mailto:bendal@apnet.cz">bendal@apnet.cz</a>
   * @since Oct 2, 2010
   */
  public static abstract class AbstractTableModel<T> implements TableModel {

    /** Values which columns are showed */
    private List<T> values; public void setValues(final List<T> values) { this.values = values; } public List<T> getValues() { return this.values; }
    @Override
    public int getRowCount() { return getValues().size(); }

    @Override
    public Widget getFieldWidget(final int row, final String colName) {
      final Label result = new Label();
      result.setText(String.valueOf(getField(this.values.get(row), colName)));
      return result;
    }

    @Override
    public boolean hasNext() { return false; }
    @Override
    public boolean hasPrevious() { return false; }

    @Override
    public void loadNext(final Repaintable reapintAfter) {}
    @Override
    public void loadPrevious(final Repaintable reapintAfter) {}

    /** Method which return value in text form
     * @param value which field is need
     * @param col column number which is need
     */
    protected abstract Object getField(T value, String colName);
  }
}
